/*
    Copyright © 2013 by Maxim Biro <nurupo.contributions@gmail.com>
    Copyright © 2014-2019 by The qTox Project Contributors

    This file is part of qTox, a Qt-based graphical interface for Tox.

    qTox is libre software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    qTox is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with qTox.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "icorefriendmessagesender.h"
#include "icoregroupmessagesender.h"
#include "icoreidhandler.h"
#include "receiptnum.h"
#include "toxfile.h"
#include "toxid.h"
#include "toxpk.h"

#include "../util/strongtype.h"
#include "../model/status.h"
#include "signal.hpp"

#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include "toxstring.h"

#include <mutex>
#include <thread>
#include <timer.hpp>

#include <functional>
#include <memory>


class CoreAV;
class CoreFile;
class CoreExt;
class IAudioControl;
class ICoreSettings;
class Profile;
class Core;
class IBootstrapListGenerator;

using ToxCorePtr = std::unique_ptr<Core>;

class Core : public ICoreFriendMessageSender,
             public ICoreIdHandler
{
public:
    enum class ToxCoreErrors
    {
        BAD_PROXY,
        INVALID_SAVE,
        FAILED_TO_START,
        ERROR_ALLOC
    };
    // recursive, since we might call our own functions
    mutable std::recursive_mutex coreLoopLock;
    static ToxCorePtr makeToxCore(const std::vector<unsigned char>& savedata, const ICoreSettings* const settings,
                                  IBootstrapListGenerator& bootstrapNodes, ToxCoreErrors* err = nullptr);
    const CoreAV* getAv() const;
    CoreAV* getAv();
    void setAv(CoreAV* coreAv);

    CoreFile* getCoreFile() const;
    Tox* getTox() const;

    const CoreExt* getExt() const;
    CoreExt* getExt();
    ~Core();

    static const std::string TOX_EXT;
    uint64_t getMaxMessageSize() const;
    std::string getPeerName(const ToxPk& id) const;
    std::vector<uint32_t> getFriendList() const;
    //GroupId getGroupPersistentId(uint32_t groupNumber) const override;
    //uint32_t getGroupNumberPeers(int groupId) const override;
    //std::string getGroupPeerName(int groupId, int peerId) const override;
    //ToxPk getGroupPeerPk(int groupId, int peerId) const override;
    //std::stringList getGroupPeerNames(int groupId) const override;
    //bool getGroupAvEnabled(int groupId) const override;
    ToxPk getFriendPublicKey(uint32_t friendNumber) const;
    std::string getFriendUsername(uint32_t friendNumber) const;

    bool isFriendOnline(uint32_t friendId) const;
    bool hasFriendWithPublicKey(const ToxPk& publicKey) const;
   // uint32_t joinGroupchat(const GroupInvite& inviteInfo);
    //void quitGroupChat(int groupId) const;

    std::string getUsername() const override;
    Status::Status getStatus() const;
    std::string getStatusMessage() const;
    ToxId getSelfId() const override;
    ToxPk getSelfPublicKey() const override;
    std::pair<std::vector<unsigned char>, std::vector<unsigned char>> getKeypair() const;

    void sendFile(uint32_t friendId, std::string filename, std::string filePath, long long filesize);

//public slots:
    void start();

    std::vector<unsigned char> getToxSaveData();

    void acceptFriendRequest(const ToxPk& friendPk);
    void requestFriendship(const ToxId& friendAddress, const std::string& message);
   // void groupInviteFriend(uint32_t friendId, int groupId);
    //int createGroup(uint8_t type = TOX_CONFERENCE_TYPE_AV);

    void removeFriend(uint32_t friendId);
    //void removeGroup(int groupId);

    void setStatus(Status::Status status);
    void setUsername(const std::string& username);
    void setStatusMessage(const std::string& message);

    bool sendMessage(uint32_t friendId, const std::string& message, ReceiptNum& receipt) override;
    //void sendGroupMessage(int groupId, const std::string& message) override;
    //void sendGroupAction(int groupId, const std::string& message) override;
   // void changeGroupTitle(int groupId, const std::string& title);
    bool sendAction(uint32_t friendId, const std::string& action, ReceiptNum& receipt) override;
    bool sendActionData(uint32_t friendId, const ToxString& actiondata, ReceiptNum& receipt) override;
    void sendTyping(uint32_t friendId, bool typing);

    void setNospam(uint32_t nospam);

//signals:
    sigslot::signal<> connected;
    sigslot::signal<> disconnected;

    sigslot::signal<const ToxPk& , const std::string& > friendRequestReceived;
    sigslot::signal<const ToxPk& , const std::vector<unsigned char>& > friendAvatarChanged;
    sigslot::signal<const ToxPk& > friendAvatarRemoved;

    sigslot::signal<const ToxPk& , const std::string& > requestSent;
    sigslot::signal<const ToxPk& , const std::string& > failedToAddFriend;
                             
    sigslot::signal<const std::string& > usernameSet;
    sigslot::signal<const std::string& > statusMessageSet;
    sigslot::signal<Status::Status > statusSet;
    sigslot::signal<const ToxId& > idSet;
                             
    sigslot::signal<const std::string& > failedToSetUsername;
    sigslot::signal<const std::string& > failedToSetStatusMessage;
    sigslot::signal<Status::Status > failedToSetStatus;
    sigslot::signal<bool > failedToSetTyping;
                             
    sigslot::signal<> saveRequest;

    /**
     * @deprecated prefer signals using ToxPk
     */

    sigslot::signal<uint32_t , uint32_t , const std::vector<unsigned char>& > fileAvatarOfferReceived;
                             
    sigslot::signal<uint32_t , const std::string& , bool > friendMessageReceived;
    sigslot::signal<uint32_t , const ToxString& , bool > friendDataReceived;
    sigslot::signal<uint32_t , const ToxPk& > friendAdded;
                             
    sigslot::signal<uint32_t , Status::Status> friendStatusChanged;
    sigslot::signal<uint32_t , const std::string& > friendStatusMessageChanged;
    sigslot::signal<uint32_t , const std::string& > friendUsernameChanged;
    sigslot::signal<uint32_t , bool  > friendTypingChanged;

    sigslot::signal<uint32_t > friendRemoved;
    //void friendLastSeenChanged(uint32_t friendId, const QDateTime& dateTime);

    //void emptyGroupCreated(int groupnumber, const GroupId groupId, const std::string& title = std::string());
//    void groupInviteReceived(const GroupInvite& inviteInfo);
//    void groupMessageReceived(int groupnumber, int peernumber, const std::string& message, bool isAction);
//    void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change);
//    void groupPeerlistChanged(int groupnumber);
//    void groupPeerNameChanged(int groupnumber, const ToxPk& peerPk, const std::string& newName);
//    void groupTitleChanged(int groupnumber, const std::string& author, const std::string& title);
//    void groupPeerAudioPlaying(int groupnumber, ToxPk peerPk);
//    void groupSentFailed(int groupId);
//    void groupJoined(int groupnumber, GroupId groupId);
    sigslot::signal<uint32_t , const std::string& , int > actionSentResult;

    sigslot::signal<int , ReceiptNum > receiptRecieved;

    sigslot::signal<uint32_t > failedToRemoveFriend;

private:
    Core( IBootstrapListGenerator& _bootstrapNodes);

    static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage,
                                size_t cMessageSize, void* core);
    static void onFriendMessage(Tox* tox, uint32_t friendId, Tox_Message_Type type,
                                const uint8_t* cMessage, size_t cMessageSize, void* core);
    static void onFriendNameChange(Tox* tox, uint32_t friendId, const uint8_t* cName,
                                   size_t cNameSize, void* core);
    static void onFriendTypingChange(Tox* tox, uint32_t friendId, bool isTyping, void* core);
    static void onStatusMessageChanged(Tox* tox, uint32_t friendId, const uint8_t* cMessage,
                                       size_t cMessageSize, void* core);
    static void onUserStatusChanged(Tox* tox, uint32_t friendId, Tox_User_Status userstatus,
                                    void* core);
    static void onConnectionStatusChanged(Tox* tox, uint32_t friendId, Tox_Connection status,
                                          void* vCore);
    //static void onGroupInvite(Tox* tox, uint32_t friendId, Tox_Conference_Type type,
     //                         const uint8_t* cookie, size_t length, void* vCore);
    //static void onGroupMessage(Tox* tox, uint32_t groupId, uint32_t peerId, Tox_Message_Type type,
      //                         const uint8_t* cMessage, size_t length, void* vCore);
    //static void onGroupPeerListChange(Tox*, uint32_t groupId, void* core);
    //static void onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* name,
     //                                 size_t length, void* core);
    //static void onGroupTitleChange(Tox* tox, uint32_t groupId, uint32_t peerId,
     //                              const uint8_t* cTitle, size_t length, void* vCore);

    static void onLosslessPacket(Tox* tox, uint32_t friendId,
                                   const uint8_t* data, size_t length, void* core);
    static void onReadReceiptCallback(Tox* tox, uint32_t friendId, uint32_t receipt, void* core);

    void sendGroupMessageWithType(int groupId, const std::string& message, Tox_Message_Type type);
    bool sendMessageWithType(uint32_t friendId, const std::string& message, Tox_Message_Type type, ReceiptNum& receipt);
    bool sendDataWithType(uint32_t friendId, const ToxString& data, Tox_Message_Type type, ReceiptNum& receipt);
    bool checkConnection();

    void makeTox(std::vector<unsigned char> savedata, ICoreSettings* s);
    void loadFriends();
    //void loadGroups();
    void bootstrapDht();

    void checkLastOnline(uint32_t friendId);

    std::string getFriendRequestErrorMessage(const ToxId& friendId, const std::string& message) const;
    static void registerCallbacks(Tox* tox);

    void process();
    void onStarted();

private:
    struct ToxDeleter
    {
        void operator()(Tox* tox)
        {
            tox_kill(tox);
        }
    };

    using ToxPtr = std::unique_ptr<Tox, ToxDeleter>;
    ToxPtr tox;

    std::unique_ptr<CoreFile> file;
    CoreAV* av = nullptr;
    std::unique_ptr<CoreExt> ext;
    CppTime::Timer toxTimer;


    //std::unique_ptr<std::thread> coreThread;
    IBootstrapListGenerator& bootstrapNodes;
};
